Catalyst / admin/Synapse-Sonar 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Sonar

public

Attack Surface Simulation

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Synapse-Sonar / synapse-sonar / app / api / assets / [id] / blast-radius / route.ts 1286 B · main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { NextRequest, NextResponse } from "next/server";
import { requireTenantContext, HttpError } from "@/lib/auth";
import { assertCan } from "@/lib/rbac";
import { computeBlastRadius } from "@/lib/blastRadius";

export const runtime = "nodejs";

/**
 * GET /api/assets/:id/blast-radius?hops=2
 *
 * Returns downstream assets reachable from the (assumed compromised) node
 * within `hops`, plus the edge IDs forming the lateral path. Tenant-scoped and
 * gated behind the graph:simulate permission (Analyst+).
 */
export async function GET(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const ctx = await requireTenantContext();
    assertCan(ctx.role, "graph:simulate");

    const hopsParam = req.nextUrl.searchParams.get("hops");
    const hops = Math.min(Math.max(parseInt(hopsParam ?? "2", 10) || 2, 1), 5);

    const result = await computeBlastRadius(ctx.tenantId, params.id, hops);
    return NextResponse.json(result);
  } catch (err) {
    if (err instanceof HttpError) {
      return NextResponse.json({ error: err.message }, { status: err.status });
    }
    const status = (err as { status?: number }).status ?? 500;
    return NextResponse.json(
      { error: (err as Error).message ?? "Internal error" },
      { status }
    );
  }
}